{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用uiautomation和路路通PC端程序生成时刻表  \n",
    "<b>目前路路通PC段和移动端当前数据库均不能提供时刻表中的检票口信息，无法得知具体股道故使用映射</b>    \n",
    "  \n",
    "\n",
    "## 当前状态  \n",
    "- 在PC端使用本地数据库其效率高于爬虫但是过分依赖离线数据库自身  \n",
    "    - 处理效率较高，一分钟可生成50条时刻表数据    \n",
    "        - 这里使用生产者-消费者模型，生产者获取离线数据库数据，消费者生成时刻表  \n",
    "        - 使用队列来传递数据，消费者速度远快于生产者，考虑多开生产者线程  \n",
    "    - 对于发布的离线数据库依赖较大  \n",
    "        - 信息准确性高，易于生成径路等数据  \n",
    "        - 目前版本数据库没有检票口数据，导致只能使用映射  \n",
    "        - 在早先版本存在检票口数据时获取的数据质量不佳处理难度相对较大(存在数据黏连)     \n",
    "- 目前爬取效果,处理等和前面武汉枢纽相同\n",
    "    - 基本实现自动化,需要修改的数据主要为游戏车站信息，线路信息用于对应列车前序站和后续站以生成径路以及中心车站自身基本信息  \n",
    "    - 最后生成时可能会由于调图等原因(当日不开行)有多个相同车次，去重即可  \n",
    "  \n",
    "- 完善度表格:    <br>\n",
    "  \n",
    "|组件信息|完善程度|当前状态|日后计划|  \n",
    "|---|---|---|---|   \n",
    "|车站|完善|可以按照单一车站进行查询|暂无|  \n",
    "|车次|完善|可以获得单一车次给出的大部分信息|暂无|  \n",
    "|检票口/股道|目前数据库版本无信息，<br>但是已有对应处理部分|暂时使用径路规则映射站台和线路所 | 优化逻辑|  \n",
    "|立折车辆|纯手动，没有处理|手动查找修改|使用甘特图手动查看修改|  \n",
    "|越行车辆|纯手动，完全没有处理|没有计划|可能会依照大站来获取信息|  \n",
    "  \n",
    "## 使用的库函数信息和达成的基本效果\n",
    "<b>大体分为了三个部分</b>  \n",
    "- 生产者-获取整体信息  \n",
    "    - 从PC端程序直接获取(全选+复制)原生字符串信息并插入队列  \n",
    "    - 视为一个对象的行为合并为一个类\n",
    "- 消费者-数据处理/数据分析\n",
    "    - 从队列获取数据并整理出时刻表\n",
    "    - 部分基于之前数据处理部分，经过一定的修改  \n",
    "    - 视为数据的流处理按照函数处理  \n",
    "- 可视化--开发中\n",
    "    - 生成甘特图以进行早期冲突查询\n",
    "\n",
    "----\n",
    "## 第一部分，定义使用的库函数和常量信息\n",
    "### 内容\n",
    "库函数主要分为两种：获取数据--处理数据--以及多线程用的函数。  \n",
    "常量部分主要用于处理数据部分，映射生成符合游戏格式的时刻表，这部分常量会随着函数的完善而更为精简  \n",
    "基于进场线路和立场线路生成线路所和站台信息  \n",
    "### 待完善/优化的部分\n",
    "1. 暂无   \n",
    "2. ....."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import uiautomation as auto\n",
    "import subprocess\n",
    "import datetime\n",
    "import time\n",
    "import itertools\n",
    "import multiprocessing\n",
    "from multiprocessing.queues import Queue\n",
    "import numpy\n",
    "import pandas\n",
    "import random\n",
    "\n",
    "\n",
    "# 速度和编组以及类型映射关系,0为普速1为动车2为高速，后续修改\n",
    "species1 = {'K': ['120', 'LCPPPPPPPP', \"0\"], 'T': ['140', 'LCPPPPPPPP', \"0\"], 'Z': ['160', 'LCPPPPPPPP', \"0\"],\n",
    "            'D': ['250', 'LLPPPPLL', \"1\"], 'C': ['200', 'LPPL', \"1\"], 'G': ['350', 'LPPLLPPL', \"2\"]}\n",
    "species1default = ['120', 'LCPPPPPPP', \"0\"]\n",
    "# 车站-编号,掉向,用时以及运行车辆种类映射关系\n",
    "# 图片左(0)右(1)侧线路key值相同则掉向,\n",
    "# 国铁车辆行走左侧,2为数据为左侧股道编号\n",
    "\n",
    "# [车站编号,车站所在侧(没用了已经),车辆进场股道,车辆离场行走股道,到达中心车站所用时间]\n",
    "gameStationInfo = {'武汉站': ['a', -1, 0, 0, 0], '动车所': ['b', 0, 0, 0, 20],\n",
    "                   '汉口': ['e', 0, 2, 1, 5],\n",
    "                   '孝感北': ['d', 0, 1, 2, 4], '红安西': ['k', -1, 1, 2, 4],\n",
    "                   '咸宁北': ['c', 0, 2, 1, 3],\n",
    "                   '鄂州': ['f', -1, 2, 1, 5], '黄冈': ['g', 0, 2, 1, 5],\n",
    "                   '葛店南站': ['葛店南', -1, 0, 0, 5],\n",
    "                   }\n",
    "gameStationDefault = ['未知', -1, 0, 0, 20]  # 找不到的默认设置\n",
    "\n",
    "# 线路和车站关系，主要用于从车站-值获取线路-键\n",
    "route1 = {'动车所': [\"src\", \"dst\"],\n",
    "          '汉口': [\"汉口\"], '葛店南站': [\"葛店南\"],\n",
    "          '红安西': [\"红安西\", \"麻城北\", \"六安\", \"金寨\", \"合肥\", \"合肥南\", \"南京南\"],\n",
    "          '孝感北': [\"孝感北\", \"信阳东\", \"明港东\", \"驻马店西\", \"漯河西\", \"许昌东\", \"郑州东\", \"郑州\"],\n",
    "          '咸宁北': [\"咸宁北\", \"赤壁北\", \"岳阳东\", \"汨罗东\", \"长沙南\", \"广州南\", \"广州白云\"],\n",
    "          '鄂州': [\"华容南\", \"鄂州\", \"黄石北\", \"大冶北\", \"白沙铺\", \"阳新\", \"瑞昌西\", \"庐山\", \"九江\", \"南昌西\", \"南昌\"],\n",
    "          '黄冈': [\"华容东\", \"黄冈西\", \"黄冈东\", \"浠水南\", \"蕲春西\", \"武穴北\", \"黄梅东\"],\n",
    "          }\n",
    "route1Default = [\"src\", \"dst\"]  # 找不到的默认设置"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 内容\n",
    "暂时替代数据库原生的检票口信息部分  \n",
    "基于进场线路和立场线路生成线路所和站台信息   \n",
    "线路所为固定映射，站台为该站场随机生成，注意可达性问题  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def mapEntrRoute(src, dst, arrtime, depatime):\n",
    "    # 使用同一径路的线路\n",
    "    stgp1 = \"fgh\"  # 武九客专方向-葛店南、鄂州、黄冈\n",
    "    stgp2 = \"c\"  # 咸宁北\n",
    "    stgp3 = \"dk\"  # 京广高速郑州、沪蓉线合肥方向\n",
    "    stgp4 = \"e\"  # 汉口\n",
    "    arrLinePost = \"\"\n",
    "    depaLinePost = \"\"\n",
    "    entret = 0\n",
    "    if src == \"b\":  # 站台目前直接随机\n",
    "        entret = random.randint(1, 15)\n",
    "    elif src == \"e\" and dst == \"c\":\n",
    "        entret = random.randint(7, 15)\n",
    "        arrLinePost = \"j#1#{t}#0 \".format(t=arrtime)\n",
    "    elif src == \"e\" and dst in stgp1:\n",
    "        entret = random.randint(16, 20)\n",
    "        arrLinePost = \"j#2#{t}#0 \".format(t=arrtime)\n",
    "        depaLinePost = \"p#1#{t}#0 \".format(t=depatime)\n",
    "    elif src in stgp3 and dst == \"c\":\n",
    "        entret = random.randint(7, 15)\n",
    "    elif src in stgp3 and dst in stgp1:\n",
    "        entret = random.randint(7, 15)\n",
    "        arrLinePost = \"p#3#{t}#0 \".format(t=arrtime)\n",
    "\n",
    "    elif src == \"c\" and dst == \"e\":\n",
    "        entret = random.randint(1, 8)\n",
    "        depaLinePost = \"n#2#{t}#0 \".format(t=depatime)\n",
    "    elif src == \"c\" and dst in stgp3:\n",
    "        entret = random.randint(1, 8)\n",
    "    elif src in stgp1 and dst == \"e\":\n",
    "        entret = random.randint(16, 20)\n",
    "        arrLinePost = \"p#3#{t}#0 \".format(t=arrtime)\n",
    "        depaLinePost = \"n#2#{t}#0 \".format(t=depatime)\n",
    "    elif src in stgp1 and dst in stgp3:\n",
    "        entret = random.randint(1, 8)\n",
    "        arrLinePost = \"p#1#{t}#0 \".format(t=arrtime)\n",
    "\n",
    "    else:  # 客机始发\n",
    "        entret = 0\n",
    "\n",
    "    return [arrLinePost, entret, depaLinePost]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第二部分-生产者  \n",
    "### 生产者对象  \n",
    "#### 主要部分  \n",
    "- init构造函数部分对于uiautomation自身进行初始化并传入基础参数，如记录日志等 \n",
    "- initsetting设置路路通PC端窗口设置，如当日开行车次、模糊站名等  \n",
    "- 后续的函数均为数据获取，从主窗口点选获取二级窗口中的车次数据并复制到队列内  \n",
    "- del析构函数部分完成日志记录关闭窗口最后销毁对象  \n",
    "#### 待优化部分  \n",
    "1. 增加始发终到查询以及单个车次查询以及筛选列车类型部分  \n",
    "2. ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class lltskbProcess(object):\n",
    "    def __init__(self, lltskbroute: str, traincount: int):  # 构造函数，调整设置和日志记录并启动程序\n",
    "\n",
    "        auto.uiautomation.SetGlobalSearchTimeout(10)\n",
    "        # set it to False and try again, default is False\n",
    "        auto.uiautomation.DEBUG_EXIST_DISAPPEAR = True\n",
    "        # set it to False and try again, default is False\n",
    "        auto.uiautomation.DEBUG_SEARCH_TIME = True\n",
    "        subprocess.Popen(\n",
    "            \"start {route}\".format(route=lltskbroute), shell=True)\n",
    "        self.window = auto.WindowControl(searchDepth=1, ClassName='#32770')\n",
    "        self.window.SetActive()  # 打开程序并激活窗口\n",
    "        self.rowcount = 18\n",
    "        self.pagecount = int(traincount/18)+1\n",
    "        return\n",
    "\n",
    "    def initsetting(self, querytype=1):  # 对于车型选择（未加入），模糊车站等选择\n",
    "        # 1为始发站，2为终到站，3为车站查询输入的车站，foundindex不从0开始\n",
    "        # if querytype==1:\n",
    "        self.stationquery = self.window.EditControl(\n",
    "            searchDepth=2, foundIndex=3, AutomationId=\"1001\")\n",
    "        self.stationquery.Click()  # 点击以激活输入框\n",
    "\n",
    "        # 返回值为0为未选中，返回值1为选中\n",
    "        isoperate = auto.CheckBoxControl(\n",
    "            searchDepth=2, AutomationId=\"1024\")  # 当日不开行\n",
    "        if isoperate.GetTogglePattern().ToggleState == 1:\n",
    "            isoperate.Click()  # 勾选以隐藏未开行车次\n",
    "\n",
    "        # 返回值为0为未选中，返回值1为选中\n",
    "        isobscure = auto.CheckBoxControl(\n",
    "            searchDepth=2, AutomationId=\"1025\")  # 模糊车站\n",
    "        if isobscure.GetTogglePattern().ToggleState == 1:\n",
    "            isobscure.Click()  # 取消勾选以获得准确单个车站\n",
    "\n",
    "        return\n",
    "\n",
    "    def selectstation(self, station: str):  # 按照车站查询\n",
    "\n",
    "        self.stationquery.Click()  # 点击以激活输入框\n",
    "        self.stationquery.SendKeys('{Ctrl}a{Del}', waitTime=0.1)  # 全选清空内容\n",
    "        self.stationquery.SendKeys(text=station, waitTime=0.1)  # 输入需要查询的车站名称\n",
    "\n",
    "        self.quetybtn = self.window.ButtonControl(\n",
    "            searchDepth=1, foundIndex=1, AutomationId=\"1006\")  # 查询按钮\n",
    "        self.quetybtn.Click(waitTime=0.1)  # 点击查询按钮\n",
    "\n",
    "        return\n",
    "\n",
    "    def selecttrain(self, train: str):  # 按照单个车次查询部分，和上面几乎一致\n",
    "\n",
    "        return\n",
    "\n",
    "    def generateroute(self, InfoQueueProd, trainCodeQueueProd):\n",
    "\n",
    "        list1 = self.window.PaneControl(searchDepth=1, foundIndex=1,\n",
    "                                        AutomationId=\"1019\")  # 获取PaneControl控件，一级页面\n",
    "        list1.Click(waitTime=0.1)  # 第一次点击激活首页的表格，但是同时会激活单一车次二级页面并聚焦\n",
    "        list2 = self.window.PaneControl(searchDepth=2, foundIndex=1,\n",
    "                                        AutomationId=\"1019\")  # 获取PaneControl控件，一级页面\n",
    "        list1.Click(x=20, y=30, waitTime=0.1)  # 第二次继续点击首页表格重新激活一级表格并选中第一行\n",
    "        pgdnbtn = auto.ButtonControl(searchDepth=4,\n",
    "                                     AutomationId=\"DownPageButton\")  # 翻页按钮\n",
    "        # for i, j in itertools.product(range(0, pagecount), range(0, rowcount)):\n",
    "        for j in range(0, self.pagecount):  # 翻页\n",
    "            for i in range(0, self.rowcount):  # 一页\n",
    "                # 每一次激活新的二级页面y值向下移动25左右\n",
    "                list1.Click(x=20, y=30+23*i, waitTime=0.1)\n",
    "\n",
    "                list2.Click(waitTime=0.1)  # 激活二级页面\n",
    "                list2.SendKeys('{Ctrl}a{Ctrl}c', waitTime=0.1)  # 全选并复制内容\n",
    "                result = auto.GetClipboardText()  # 复制到剪切板\n",
    "                InfoQueueProd.put(result, timeout=1)  # 加入队列\n",
    "                traincode = list2.GetParentControl().Name\n",
    "                trainCodeQueueProd.put(traincode, timeout=1)  # 将列车号即二级页面标题\n",
    "            try:\n",
    "                pgdnbtn.Click()  # 下一页按钮，大小会改变，应该需要重新选择?到底了多点两次似乎也不算报错\n",
    "            except LookupError as le:\n",
    "                print(le)\n",
    "                break\n",
    "        return\n",
    "\n",
    "    def __del__(self,):  # 析构函数，记录日志关闭窗口\n",
    "        auto.Logger.Write('Data acquisition completed.\\n',\n",
    "                          auto.ConsoleColor.Cyan)\n",
    "        self.window.Disappears(1)  # 确认窗口状态并关闭\n",
    "        self.window.GetWindowPattern().Close()\n",
    "        self.window.Exists(1)  # 记录日志\n",
    "\n",
    "        return\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 生产者工作函数  \n",
    "- 初始化生成者--传入队列等信息，后续多线程主函数调用  \n",
    "- 析构销毁等待完成后自动进行  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "\n",
    "def obtaintrain(lltroute: str, tc: int, tarstation: str, InfoQueue: Queue, trainCodeQueue: Queue):\n",
    "    # 生产者进程函数\n",
    "    lltpro = lltskbProcess(\n",
    "        lltskbroute=lltroute, traincount=tc)\n",
    "    lltpro.initsetting()  # 处理获取时刻表信息\n",
    "    lltpro.selectstation(station=tarstation)\n",
    "    lltpro.generateroute(InfoQueue, trainCodeQueue)\n",
    "\n",
    "    return\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 第三部分-消费者  \n",
    "### 消费者函数部分  \n",
    "#### 主要内容\n",
    "依照数据流处理的想法进行编写，所有函数都是只对于一条数据进行处理  \n",
    "由于大部分继承之前按照文本批处理的函数，所以显得比较冗长  \n",
    "主要分为三部分\n",
    "- initformate 预处理部分重新编写用于对从全局队列中拿到的数据进行预处理-替换异常值\n",
    "- generateroute 生成径路部分继承自之前批处理部分， 映射对应路径并处理时间部分\n",
    "- generategame 生成游戏字符串部分同样继承自批处理，生成最终游戏时刻表字符并写入文件\n",
    "- reproduct 处理生成完毕的文件，主要用于去重(<strong> 可选部分，暂未使用</strong >)  \n",
    "\n",
    "#### 待优化部分\n",
    "1. 优化处理逻辑，更多的适配流处理的思想\n",
    "2. 优化时间处理部分，尤其是始发终到车  \n",
    "3. ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def initStrFormate(station: str, initcodeStr: str, initInfoStr: str):\n",
    "    # 使用最初得到的字符数据\n",
    "    traincodeStr = initcodeStr.replace(\"次\", \"\")  # 字符串样式车次便于最后生成游戏样式的字符串\n",
    "    if \"B\" in traincodeStr:  # 一日开行两次的车辆，会导致分割异常\n",
    "        traincodeStr = traincodeStr.replace(\"B\", \"\")\n",
    "    traincodeList = traincodeStr.split(sep=\"/\")  # 列表样式便于下面处理\n",
    "    if len(traincodeList) == 2:  # 如果变化车次，使用0号位车次替代便于一起处理\n",
    "        initInfoStr = initInfoStr.replace(\n",
    "            traincodeList[1], traincodeList[0])  # [1:]\n",
    "\n",
    "    # 按照车次数字部分分割，因为复制过来车次有首位缺失\n",
    "    initList = initInfoStr.split(traincodeList[0])\n",
    "    tarposi = 0\n",
    "    for i, e in enumerate(initList):\n",
    "        if station+\"\\t\" in e:  # 目标车站\n",
    "            tarposi = i  # 获取目标车站位置，暂不考虑环线停靠两次等\n",
    "            break\n",
    "    prepareList = [[], [], []]  # 长度为3的空列表\n",
    "    if tarposi == 1:  # 始发车,0位元素为可能剩下的列车类型字母cdg等\n",
    "        prepareList = [[traincodeStr, \"src\", \"00:00\", \"00:00\", \"0\", \"0\"],\n",
    "                       (traincodeStr + initList[1]\n",
    "                        ).split(sep=\"\\t\", maxsplit=6),\n",
    "                       (traincodeStr+initList[2]).split(sep=\"\\t\", maxsplit=6)]\n",
    "    elif tarposi == len(initList)-1:  # 终到车\n",
    "        prepareList = [(traincodeStr+initList[tarposi-1]).split(sep=\"\\t\", maxsplit=6),\n",
    "                       (traincodeStr + initList[tarposi]\n",
    "                        ).split(sep=\"\\t\", maxsplit=6),\n",
    "                       [traincodeStr, \"dst\", \"23:59\", \"23:59\", \"9999\", \"0\"]]\n",
    "    else:  # 过路车\n",
    "        prepareList = [(traincodeStr+initList[tarposi-1]).split(sep=\"\\t\", maxsplit=6),\n",
    "                       (traincodeStr + initList[tarposi]\n",
    "                        ).split(sep=\"\\t\", maxsplit=6),\n",
    "                       (traincodeStr+initList[tarposi+1]).split(sep=\"\\t\", maxsplit=6)]\n",
    "\n",
    "    for i in range(3):\n",
    "        while len(prepareList[i]) <= 6:  # 如果最后一列缺失\n",
    "            prepareList[i].append(\"0\")\n",
    "        while len(prepareList[i]) > 6:  # 超长的截取\n",
    "            prepareList[i].pop()\n",
    "        if prepareList[i][2] == \"-- --\":  # 替换中心车站时间从-- --到到达时间\n",
    "            prepareList[i][2] = prepareList[i][3]\n",
    "        elif prepareList[i][3] == \"-- --\":  # 替换进场车站离开时间\n",
    "            prepareList[i][3] = prepareList[i][2]\n",
    "\n",
    "    return prepareList\n",
    "\n",
    "\n",
    "def routeStrFormate(prepareList: list[list]):\n",
    "    # 使用上面初步处理的数据帧并完成进一步处理\n",
    "    # 时间处理部分\n",
    "    traindf = pandas.DataFrame(data=prepareList, index=None, columns=[\n",
    "        \"traincode\", \"station\", \"arrival\", \"departure\", \"totalmiles\", \"entrance\"])\n",
    "    traindf[\"arrival\"] = pandas.to_datetime(\n",
    "        arg=traindf[\"arrival\"],  format=\"%H:%M\")\n",
    "    traindf[\"departure\"] = pandas.to_datetime(\n",
    "        arg=traindf[\"departure\"],  format=\"%H:%M\")\n",
    "    traindf[\"stoptime\"] = (traindf[\"departure\"] -\n",
    "                           traindf[\"arrival\"]).dt.total_seconds() / 60\n",
    "    traindf[\"stoptime\"] = traindf[\"stoptime\"].apply(lambda x: int(x))\n",
    "\n",
    "    # print(traindf)\n",
    "    traingpdf = traindf.groupby(\n",
    "        by=\"traincode\", as_index=False, sort=False).agg(list)\n",
    "\n",
    "    # dataframe--series--list,三级索引，0和2为前后站索引\n",
    "    try:\n",
    "        for k, v in route1.items():\n",
    "            # 把车站名映射为线路，前后站一定不同所以可以循环判断两次\n",
    "            if traingpdf[\"station\"][0][0] in v:\n",
    "                traingpdf[\"station\"][0][0] = k\n",
    "            elif traingpdf[\"station\"][0][2] in v:\n",
    "                traingpdf[\"station\"][0][2] = k\n",
    "    except IndexError:  # 可能会有的超出长度，没有复现成功\n",
    "        print(traingpdf[\"traincode\"], \"出现异常\")\n",
    "        traingpdf[\"station\"][0][0] = traingpdf[\"station\"][0][1]\n",
    "        traingpdf[\"station\"][0][2] = traingpdf[\"station\"][0][1]\n",
    "\n",
    "    # arriveroute = traingpdf[\"station\"][0][0]\n",
    "    # leaveroute = traingpdf[\"station\"][0][2]\n",
    "    # 计算理论进场离场时间\n",
    "    traingpdf[\"arrival\"][0][0] = traingpdf[\"arrival\"][0][1] - \\\n",
    "        datetime.timedelta(\n",
    "            minutes=(gameStationInfo.get(traingpdf[\"station\"][0][0], gameStationDefault))[4])\n",
    "    traingpdf[\"departure\"][0][2] = traingpdf[\"departure\"][0][1] + \\\n",
    "        datetime.timedelta(\n",
    "            minutes=(gameStationInfo.get(traingpdf[\"station\"][0][2], gameStationDefault))[4])\n",
    "\n",
    "    return traingpdf\n",
    "#\n",
    "\n",
    "\n",
    "def generateGameStr(infodf: pandas.DataFrame, entRule=[]):\n",
    "    # 最后生成游戏字符串，entrule为股道/检票口在entrance部分的位置范围\n",
    "    # D5769/D5772 COMMUTER 200 LPPL X1 : 余花联络线南湖东方向#1#19:28:00#0 武汉东#2#19:34:00#5 余花联络线花山南方向###0\n",
    "    # 车辆信息部分\n",
    "    trcode = infodf[\"traincode\"][0]\n",
    "    trtype = trcode[0]\n",
    "    timfo = species1.get(trtype, ['120', 'LCPPPPPP', \"0\"])\n",
    "    trainStr = \"{tc} COMMUTER {spe} {mar} X1 : \".format(\n",
    "        tc=trcode, spe=timfo[0], mar=timfo[1])\n",
    "    # 进场和立场部分\n",
    "    ast = infodf[\"station\"][0][0]  # 进场部分\n",
    "    astref = gameStationInfo.get(ast, gameStationDefault)\n",
    "    arrStr = \"{sta}#{rail}#{clck}#{stay} \".format(\n",
    "        sta=astref[0], rail=astref[2], clck=infodf[\"arrival\"][0][0], stay=0)\n",
    "    dst = infodf[\"station\"][0][2]  # 离场部分\n",
    "    dstref = gameStationInfo.get(dst, gameStationDefault)\n",
    "    depStr = \"{sta}#{rail}#{clck}#{stay} \".format(\n",
    "        sta=dstref[0], rail=dstref[3], clck=infodf[\"departure\"][0][2], stay=0)\n",
    "    # 核心车站部分\n",
    "    cst = infodf[\"station\"][0][1]  # 停站部分\n",
    "    erl = mapEntrRoute(src=astref[0], dst=dstref[0], arrtime=infodf[\"arrival\"]\n",
    "                       [0][0], depatime=infodf[\"departure\"][0][2])\n",
    "    sta1 = infodf[\"stoptime\"][0][1]\n",
    "\n",
    "    centerStr = \"{sta}#{rail}#{clck}#{stay} \".format(\n",
    "        sta=\"a\", rail=erl[1], clck=infodf[\"arrival\"][0][1], stay=sta1 if sta1 != 0 else 1)\n",
    "    # 字符串加上线路所\n",
    "    res = (trainStr+arrStr+erl[0]+centerStr +\n",
    "           erl[2]+depStr).replace(\"1900-01-01 \", \"\")\n",
    "    # print(res)\n",
    "    return res\n",
    "\n",
    "\n",
    "def strReproduct(file1: str, file2: str):\n",
    "    codelist = []\n",
    "    infolist = []\n",
    "    with open(file=file1, mode=\"r\", encoding=\"utf-8\") as f:\n",
    "        for li in f:\n",
    "            info1 = li\n",
    "            code1 = li.split(sep=\" \", maxsplit=1)[0]\n",
    "            if code1 not in codelist:\n",
    "                codelist.append(code1)\n",
    "                infolist.append(info1)\n",
    "            else:\n",
    "                continue\n",
    "    f.close()\n",
    "    infolist.sort()\n",
    "    with open(file=file2, mode=\"w\", encoding=\"utf-8\") as w:\n",
    "        for i in infolist:\n",
    "            w.writelines(i)\n",
    "    w.close()\n",
    "\n",
    "    return"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 消费者工作函数部分  \n",
    "#### 主要内容   \n",
    "使用死循环进行流处理，在所有数据均处理完毕后写入文件  \n",
    "- 使用查空和哨兵值优化性能表现减少占用\n",
    "    - 如果队列为空则等待5s(消费者远快于生产者，时间还可以在长)，后续等待完成在处理  \n",
    "    - 在接收到magic number后停止退出，完成处理部分\n",
    "- 数据通过多个函数接续处理，流程函数参考上面，处理完成后加入集合先期去重    \n",
    "- 最后统一写到文件中  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def gameStrProcess(station, InfoQueueCons: Queue, trainCodeQueueCons: Queue, outfile: str):\n",
    "    resset = set()\n",
    "    while True:\n",
    "        if InfoQueueCons.empty() == True:\n",
    "            print(\"队列已为空，暂时无元素可初步处理，消费者暂停5s\")\n",
    "            time.sleep(5)\n",
    "            continue\n",
    "        initInfo = InfoQueueCons.get(timeout=1)\n",
    "        if initInfo == \"114514\": #magic number\n",
    "            break #处理完成退出\n",
    "        initcode = trainCodeQueueCons.get(timeout=1)\n",
    "        # 接续处理 \n",
    "        initpr1 = initStrFormate(\n",
    "            station=station, initcodeStr=initcode, initInfoStr=initInfo)\n",
    "        nextpr2 = routeStrFormate(prepareList=initpr1)\n",
    "        outstr = generateGameStr(nextpr2, [0, 0])\n",
    "        resset.add(outstr)\n",
    "    print(resset) #写入文件 \n",
    "    f = open(file=outfile, mode=\"w\", encoding=\"utf-8\")\n",
    "    for rs in resset:\n",
    "        f.write(rs+\"\\n\")\n",
    "        print(rs)\n",
    "    f.close()\n",
    "    return\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 第三部分--主函数部分\n",
    "#### 使用生产者消费者模型建立任务  \n",
    "分别建立生产者和消费者并启动\n",
    "\n",
    "\n",
    "#### 待优化\n",
    "1. 多开几个生产者线程，目前不会uiautomation的多线程，会抢鼠标  \n",
    "2. 使用其他数据结构来合并两个公有队列中的字符串，减少锁开销"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "if __name__ == \"__main__\":\n",
    "    InfoQueue1 = multiprocessing.Queue()  # 径路信息队列\n",
    "    trainCodeQueue1 = multiprocessing.Queue()  # 车次号队列\n",
    "    # 中继承接队列\n",
    "    targetstation = \"武汉\"\n",
    "\n",
    "    lltskbroute = \"lltskb.exe\" #最好为程序完整路径，相对路径可能不行\n",
    "    # 生产者需要设置大致上的列车数目\n",
    "    p = multiprocessing.Process(\n",
    "        target=obtaintrain, args=(lltskbroute, 550, targetstation, InfoQueue1, trainCodeQueue1,))\n",
    "    c = multiprocessing.Process(\n",
    "        target=gameStrProcess, args=(targetstation, InfoQueue1, trainCodeQueue1, \"text2.txt\"))\n",
    "\n",
    "    p.start()  # 启动生产者和消费者进程\n",
    "    time.sleep(10)  # 启动较慢，等待生产者初始化完成\n",
    "    c.start()\n",
    "\n",
    "    p.join()  # 等待生产者进程完成\n",
    "    InfoQueue1.put(\"114514\")  # magic number,通知消费者所有产品已经生产完毕\n",
    "    c.join()  # 等待消费者进程完成\n",
    "\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "uiautomation",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
